import * as Cesium from "cesium"
import { Popup } from "./Popup"
import { FlowLightLineMaterial } from "./Materials/FlowLightLine"
import { CesiumEventEmitter, EventType } from "./EventEmitter"
import type { PositionedEvent } from "./EventEmitter"

//默认相机范围
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = new Cesium.Rectangle(
  Cesium.Math.toRadians(70),
  Cesium.Math.toRadians(-15),
  Cesium.Math.toRadians(140),
  Cesium.Math.toRadians(80)
)

interface Option extends Cesium.Viewer.ConstructorOptions {
  allowCameraUnderground?: boolean
  asMapboxControl?: boolean
}

interface DirectionAndUp {
  direction: Cesium.Cartesian3
  up: Cesium.Cartesian3
}

interface HeadingPitchRoll {
  heading?: number //单位是弧度
  pitch?: number
  roll?: number
}

type CesiumCameraOrientationOption = DirectionAndUp | HeadingPitchRoll

export class Viewer extends Cesium.Viewer {
  popups: Popup[] = []
  public emitter: CesiumEventEmitter
  constructor(container: Element | string, public options?: Option) {
    //设置初始化默认参数
    super(container, {
      //是否创建动画小器件，左下角仪表
      animation: false,
      //是否显示全屏按钮
      fullscreenButton: false,
      //放大镜图标，查找位置工具，查找到之后会将镜头对准找到的地址，默认使用bing地图
      geocoder: false,
      //房子图标，是否显示Home按钮，视角返回初始位置
      homeButton: false,
      //是否显示信息框
      infoBox: false,
      //经纬网图标，选择视角的模式，有三种：3D，2D，哥伦布视图（2.5D)，是否显示3D/2D选择器
      sceneModePicker: false,
      //是否显示时间轴
      timeline: false,
      //设定3维地图的默认场景模式:Cesium.SceneMode.SCENE2D、Cesium.SceneMode.SCENE3D、Cesium.SceneMode.MORPHING
      sceneMode: Cesium.SceneMode.SCENE3D,
      //如果设置为true，则所有几何图形以3D模式绘制以节约GPU资源
      scene3DOnly: true,
      //是否显示图层选择器，可选择要显示的地图服务和地形服务
      baseLayerPicker: false,
      //问号图标，右上角导航帮助按钮，显示默认的地图控制帮助
      navigationHelpButton: false,
      //虚拟现实
      vrButton: false,
      //是否显示选取指示器组件[绿框]
      selectionIndicator: false,
      //设置背景透明,涉及透贴显示
      orderIndependentTranslucency: true,
      //开启时间动画
      shouldAnimate: true,
      //关闭默认底图以支持离线使用
      baseLayer: false,
      contextOptions: {
        webgl: {
          preserveDrawingBuffer: false, //保留绘图缓冲区：通过canvas截图需要将该项设置为true
          failIfMajorPerformanceCaveat: true, //防止在性能不佳的设备上运行
          antialias: true, //抗锯齿,
          alpha: true, //透明度支持
          powerPreference: "high-performance", //cpu偏好：优先使用高性能cpu,
        },
        requestWebgl1: false,
      },
      shadows: true, //阴影
      ...options,
    })
    this.initBaseConfig()
    this.emitter = new CesiumEventEmitter(this)
  }

  //常见基础设置
  private initBaseConfig() {
    //默认打开深度检测，那么在地形以下的对象不可见
    this.scene.globe.depthTestAgainstTerrain = true
    //是否显示星空
    this.scene.skyBox.show = true
    //是否显示太阳
    this.scene.sun.show = true
    //是否显示月亮
    this.scene.moon.show = false
    //是否开启大气层
    this.scene.skyAtmosphere.show = true
    //启用基于太阳角度的动态光照
    this.scene.globe.enableLighting = true

    /*默认开启环境光遮蔽*/
    // 检查浏览器支持性
    if (
      !Cesium.PostProcessStageLibrary.isAmbientOcclusionSupported(this.scene)
    ) {
      console.warn("当前浏览器不支持环境光遮蔽效果")
    }
    // 启用默认AO效果
    this.scene.postProcessStages.ambientOcclusion.enabled = true

    // 1.0代表真实时间速率，大于1则加速，小于1则减速
    this.clock.multiplier = 1

    //禁止相机进入地下 false允许，true禁止
    this.scene.screenSpaceCameraController.enableCollisionDetection = this
      .options
      ? this.options.allowCameraUnderground
        ? false
        : true
      : true

    //隐藏版本信息
    ;(this.cesiumWidget.creditContainer as any).style.display = "none"

    //高分辨率设备适配
    this.resolutionScale = window.devicePixelRatio

    //交互方式与mapbox统一
    if (this.options?.asMapboxControl) {
      this.asMapboxControl()
    }

    //阴影质量
    const shadowMap = this.scene.shadowMap
    shadowMap.size = 4096 //中等阴影质量
    shadowMap.softShadows = true //边缘柔和阴影

    //抗锯齿
    this.scene.postProcessStages.fxaa.enabled = true //默认开启抗锯齿
  }

  /**
   * @description 事件绑定
   *
   */
  on(type: EventType, callback: (event: PositionedEvent) => void): void {
    this.emitter.on(type, callback)
  }

  /**
   * @description 事件解绑
   *
   */
  off(type: EventType, callback?: (event: PositionedEvent) => void) {
    this.emitter.off(type, callback)
  }

  /**
   * @description cesium操作方式改为和mapboxgl的操作方式一样
   */
  asMapboxControl() {
    const { scene } = this
    scene.screenSpaceCameraController.tiltEventTypes = [
      Cesium.CameraEventType.RIGHT_DRAG,
      Cesium.CameraEventType.PINCH,
      {
        eventType: Cesium.CameraEventType.LEFT_DRAG,
        modifier: Cesium.KeyboardEventModifier.CTRL,
      },
      {
        eventType: Cesium.CameraEventType.RIGHT_DRAG,
        modifier: Cesium.KeyboardEventModifier.CTRL,
      },
    ]
    scene.screenSpaceCameraController.zoomEventTypes = [
      Cesium.CameraEventType.MIDDLE_DRAG,
      Cesium.CameraEventType.WHEEL,
      Cesium.CameraEventType.PINCH,
    ]
  }

  /**
   * @description 获取精确贴地形坐标
   * @example cesium2s.getClampPosition({viewer,lnglat:[110,20]}) //获取经度110，纬度20点的精确贴地形高度
   */
  async getClampPositions(
    positions: Cesium.Cartesian3[]
  ): Promise<Cesium.Cartesian3[]> {
    const terrainProvider = this.scene.terrainProvider
    const clampPositions = positions.map((item) =>
      Cesium.Cartographic.fromCartesian(item)
    )
    const updatedPositions = await Cesium.sampleTerrainMostDetailed(
      terrainProvider,
      clampPositions
    )
    return updatedPositions.map((item) =>
      Cesium.Cartesian3.fromRadians(item.longitude, item.latitude, item.height)
    )
  }

  /**
   * 加载gltf模型
   * @param option.position ——模型要添加的经纬度位置或Cartesian3坐标
   * @param option.modelUrl ——模型的Url
   * @param option.bearing ——模型的水平旋转
   * @param option.id ——要为模型指定的id
   * @param option.clampToGround ——模型是否贴地
   * @returns
   */
  async loadModel({
    position,
    modelUrl,
    orientation = {},
    id,
    clampToGround = false,
    silhouetteColor,
    silhouetteSize,
    scale,
  }: {
    position: [number, number, number] | Cesium.Cartesian3
    modelUrl: string
    bearing?: number
    id?: string
    clampToGround?: boolean
    silhouetteColor?: Cesium.Color
    silhouetteSize?: number
    orientation?: any
    scale?: number
  }): Promise<Cesium.Model | undefined> {
    let positionCar3: Cesium.Cartesian3 | undefined
    if (position instanceof Cesium.Cartesian3) {
      positionCar3 = position
    } else if (position instanceof Array) {
      const [lng, lat, height] = position
      let heightParam = height
      if (clampToGround) {
        //获取精确贴地形高度
        const terrainProvider = this.scene.terrainProvider
        const positions = [Cesium.Cartographic.fromDegrees(lng, lat)]
        const updatedPositions = await Cesium.sampleTerrainMostDetailed(
          terrainProvider,
          positions
        )
        heightParam = updatedPositions[0].height
        positionCar3 = Cesium.Cartesian3.fromDegrees(
          position[0],
          position[1],
          heightParam
        )
      }
    }
    if (positionCar3) {
      //根据朝向参数计算欧拉角
      let headingPitchRoll = new Cesium.HeadingPitchRoll()
      const { heading = 0, pitch = 0, roll = 0 } = orientation
      headingPitchRoll = new Cesium.HeadingPitchRoll(heading, pitch, roll)

      //计算位置矩阵
      const fixedFrameTransform =
        Cesium.Transforms.localFrameToFixedFrameGenerator("north", "west")

      //组合旋转矩阵和位置矩阵
      const modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(
        positionCar3,
        headingPitchRoll,
        Cesium.Ellipsoid.WGS84,
        fixedFrameTransform
      )

      // 添加实体并设置模型
      const model = this.scene.primitives.add(
        await Cesium.Model.fromGltfAsync({
          id,
          url: modelUrl,
          show: true, // default
          scale, // double size
          allowPicking: true,
          modelMatrix,
          silhouetteColor,
          silhouetteSize,
        })
      )
      model.imageBasedLighting.luminanceAtZenith = 0.4
      return model
    } else {
      console.error("position参数不正确", position)
      return undefined
    }
  }

  //绘制模型坐标轴
  drawModelAxes(
    position: Cesium.Cartesian3,
    orientation: Cesium.Quaternion,
    length = 10
  ) {
    // 计算坐标轴终点（在模型局部坐标系中）
    const xEnd = Cesium.Matrix4.multiplyByPoint(
      Cesium.Matrix4.fromRotationTranslation(
        Cesium.Matrix3.fromQuaternion(orientation),
        position
      ),
      new Cesium.Cartesian3(length, 0, 0),
      new Cesium.Cartesian3()
    )

    const yEnd = Cesium.Matrix4.multiplyByPoint(
      Cesium.Matrix4.fromRotationTranslation(
        Cesium.Matrix3.fromQuaternion(orientation),
        position
      ),
      new Cesium.Cartesian3(0, length, 0),
      new Cesium.Cartesian3()
    )

    const zEnd = Cesium.Matrix4.multiplyByPoint(
      Cesium.Matrix4.fromRotationTranslation(
        Cesium.Matrix3.fromQuaternion(orientation),
        position
      ),
      new Cesium.Cartesian3(0, 0, length),
      new Cesium.Cartesian3()
    )

    // 添加坐标轴线段
    this.scene.primitives.add(
      new Cesium.Primitive({
        geometryInstances: [
          // X轴（红色）
          new Cesium.GeometryInstance({
            geometry: new Cesium.PolylineGeometry({
              positions: [position, xEnd],
              width: 2,
            }),
            attributes: {
              color: Cesium.ColorGeometryInstanceAttribute.fromColor(
                Cesium.Color.RED
              ),
            },
          }),
          // Y轴（绿色）
          new Cesium.GeometryInstance({
            geometry: new Cesium.PolylineGeometry({
              positions: [position, yEnd],
              width: 2,
            }),
            attributes: {
              color: Cesium.ColorGeometryInstanceAttribute.fromColor(
                Cesium.Color.GREEN
              ),
            },
          }),
          // Z轴（蓝色）
          new Cesium.GeometryInstance({
            geometry: new Cesium.PolylineGeometry({
              positions: [position, zEnd],
              width: 2,
            }),
            attributes: {
              color: Cesium.ColorGeometryInstanceAttribute.fromColor(
                Cesium.Color.BLUE
              ),
            },
          }),
        ],
        appearance: new Cesium.PolylineColorAppearance(),
      })
    )
  }

  /**
   * 加载cesium3dtiles模型
   * @param scene ——cesium场景
   * @param url ——tileset.json的url
   * @param options ——cesium支持的3dtileset相关配置
   * @example 
   * //加载3dtilest模型，并飞过去、调整高度；
   * const tileset = await cesium2s.load3dtiles(
      "/models/3dtiles/building/tileset.json"
     )
     viewer.flyTo(tileset)
     cesium2s.adjust3DTilesetHeight(tileset,40) //向上平移40米
   */
  async load3dtiles(
    url: string,
    options?: Cesium.Cesium3DTileset.ConstructorOptions
  ): Promise<Cesium.Cesium3DTileset> {
    try {
      const tileset = await Cesium.Cesium3DTileset.fromUrl(url, options)
      this.scene.primitives.add(tileset)
      return tileset
    } catch (error) {
      console.error(`Error creating tileset: ${error}`)
      throw error
    }
  }

  /**
   * @description 多边形geojson转gml格式
   * @param multiPolygon
   * @param srsCode
   * @returns
   */
  multiPolygonToGml(
    multiPolygon: GeoJSON.Feature<GeoJSON.MultiPolygon>,
    srsCode?: number
  ) {
    const multipolygon = multiPolygon.geometry.coordinates
    const srsCodeParam = srsCode ? srsCode : 4326
    // Create GML structure
    let gmlXml = `<gml:MultiPolygon srsName="EPSG:${srsCodeParam}">\n`

    // Iterate over each polygon in the multipolygon
    multipolygon.forEach((polygonCoords: any) => {
      gmlXml += `  <gml:polygonMember>\n`
      gmlXml += `    <gml:Polygon>\n`
      gmlXml += `      <gml:exterior>\n`
      gmlXml += `        <gml:LinearRing>\n`
      gmlXml += `          <gml:coordinates>`

      // Convert coordinates to GML format
      const coordinates = polygonCoords[0]
        .map((coord: number[]) => `${coord[0]},${coord[1]}`)
        .join(" ")

      gmlXml += `${coordinates}</gml:coordinates>\n`
      gmlXml += `        </gml:LinearRing>\n`
      gmlXml += `      </gml:exterior>\n`
      gmlXml += `    </gml:Polygon>\n`
      gmlXml += `  </gml:polygonMember>\n`
    })

    gmlXml += `</gml:MultiPolygon>\n`

    return gmlXml
  }

  /**
   * @description 弹窗
   */
  addPopup(params: {
    popDom: HTMLElement
    position: Cesium.Cartesian3
    offset?: [number, number]
    id?: string
  }) {
    const popup = new Popup({
      viewer: this,
      ...params,
    })
    this.popups.push(popup)
    return popup
  }

  /**
   * @description 移除地图上的弹窗
   */
  removePopup(popupId: string): boolean {
    const popupIndex = this.popups.findIndex((popup) => popup.id === popupId)
    if (popupIndex !== -1) {
      const popupToRemove = this.popups[popupIndex]
      this.popups.splice(popupIndex, 1)
      popupToRemove.destroy()
      return true
    } else {
      console.warn(`Popup with ID ${popupId} not found.`)
      return false
    }
  }

  /**
   * @description 缩放到要素
   * @param padding 边距百分比，默认为0.2倍
   */
  flyToPoints({
    points,
    padding = 0.2,
  }: {
    points: Cesium.Cartesian3[]
    padding: number
  }) {
    if (points.length > 1) {
      let rectangle = Cesium.Rectangle.fromCartesianArray(points)
      const lngRange = rectangle.east - rectangle.west
      const latRange = rectangle.north - rectangle.south
      let lngExpendValue = lngRange * (padding / 2)
      let latExpendValue = latRange * (padding / 2)

      //飞行范围过小时做特殊处理
      if (lngExpendValue < 0.0001 || latExpendValue < 0.0001) {
        lngExpendValue = 0.0002
        latExpendValue = 0.0002
      }

      // 根据 padding 调整 rectangle 的边界
      rectangle = new Cesium.Rectangle(
        rectangle.west - lngExpendValue, // 向左扩展
        rectangle.south - latExpendValue, // 向下扩展
        rectangle.east + lngExpendValue, // 向右扩展
        rectangle.north + latExpendValue // 向上扩展
      )

      this.camera.flyTo({
        destination: rectangle,
        duration: 1.2,
      })
    } else {
      console.error("Error in flyToPoints ：参数错误")
    }
  }

  /**
   * @description 添加流光线
   * @returns
   */
  addFlowLightLine({
    positions,
    color = Cesium.Color.YELLOW,
    width = 5,
    speed = 1,
  }: {
    positions: Cesium.Cartesian3[]
    color?: Cesium.Color
    width?: number
    speed?: number
  }) {
    const material = new FlowLightLineMaterial({
      color: color || Cesium.Color.YELLOW,
    })
    // 创建一个图元
    const linePrimitive = new Cesium.Primitive({
      geometryInstances: new Cesium.GeometryInstance({
        geometry: new Cesium.PolylineGeometry({
          positions,
          width,
        }),
      }),
      appearance: new Cesium.PolylineMaterialAppearance({
        material,
      }),
    })
    this.scene.groundPrimitives.add(linePrimitive)

    // 定义一个自增变量用于模拟流动时间
    let simulatedTime = 0.0
    // 定义一个流动速度因子，可以调整这个值控制流动速度
    const flowSpeed = 0.015 * speed

    const callback = () => {
      // 每一帧渲染时自增 simulatedTime，控制其递增速度
      simulatedTime += flowSpeed
      // 如果 simulatedTime 超过 1.0，重置为 0.0，确保在 0-1 范围内循环
      simulatedTime = simulatedTime % 1.0
      // 更新材质中的 time 参数
      material.uniforms.time = simulatedTime
    }

    // 在每帧渲染时更新 time
    this.scene.preRender.addEventListener(callback)

    return linePrimitive
  }

  //添加注记(支持背景图)
  addLabel(params: {
    viewer: Cesium.Viewer
    label: string
    backgroundImage: string
    lnglat: [number, number]
  }) {
    const {
      viewer,
      label,
      backgroundImage,
      lnglat: [lng, lat],
    } = params

    // 创建一个Canvas
    const canvas = document.createElement("canvas")
    const context = canvas.getContext("2d")
    if (!context) return

    // 加载背景图
    const image = new Image()
    image.src = backgroundImage
    image.onload = () => {
      // 设置Canvas宽高为图片的宽高
      canvas.width = image.width
      canvas.height = image.height

      // 绘制背景图
      context.drawImage(image, 0, 0, canvas.width, canvas.height)

      // 设置文本样式
      context.fillStyle = "#FFFFFF" // 白色字体
      context.font = "24px sans-serif" // 字体样式
      context.textAlign = "center" // 文本对齐方式
      context.textBaseline = "middle" // 文本基线

      // 绘制文本，文本位置居中
      context.fillText(label, canvas.width / 2, canvas.height / 2)

      // 创建 Billboard
      viewer.entities.add({
        position: Cesium.Cartesian3.fromDegrees(lng, lat), // 设置位置，默认(0, 0)，需要根据需求更改
        billboard: {
          image: canvas.toDataURL(), // 将Canvas转换为图像
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 控制图像的锚点位置
          heightReference: Cesium.HeightReference.CLAMP_TO_TERRAIN,
          pixelOffset: new Cesium.Cartesian2(0, -50),
        },
      })
    }
  }

  //判断点是否可见
  isPointVisible(position: Cesium.Cartesian3): boolean {
    const { camera, scene } = this

    // 从相机位置到目标点生成射线
    const ray = new Cesium.Ray(
      camera.position,
      Cesium.Cartesian3.normalize(
        Cesium.Cartesian3.subtract(
          position,
          camera.position,
          new Cesium.Cartesian3()
        ),
        new Cesium.Cartesian3()
      )
    )

    // 使用 scene.globe.pick 来检测射线与地形的交点
    const intersection = scene.globe.pick(ray, scene)

    if (intersection) {
      // 计算相机到地形交点的距离
      const distanceToIntersection = Cesium.Cartesian3.distance(
        camera.position,
        intersection
      )
      const distanceToTarget = Cesium.Cartesian3.distance(
        camera.position,
        position
      )

      if (distanceToIntersection < distanceToTarget) {
        // 在到达目标点之前有物体遮挡
        return false
      }
    }

    // 没有检测到遮挡，点可见
    return true
  }

  /**
   * @description 添加自定义xyz瓦片
   * @template viewer.addImageryLayer({
        url: item.url,
        credit: item.id,
        maximumLevel:item.maxZoom || 16
      })
   */
  addImageryLayer(
    options: Cesium.UrlTemplateImageryProvider.ConstructorOptions
  ): Cesium.ImageryLayer {
    // 添加 XYZ 瓦片影像图层
    return this.imageryLayers.addImageryProvider(
      new Cesium.UrlTemplateImageryProvider(options)
    )
  }

  /**
   * @description 获取相机视角参数
   */
  getCameraOption() {
    const {
      position: { x, y, z },
      heading,
      pitch,
      roll,
    } = this.camera

    return {
      destination: `new Cesium.Cartesian3(${x},${y},${z})`,
      orientation: {
        heading,
        pitch,
        roll,
      },
    }
  }
}
